{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Weight Decay\n",
    "\n",
    ":label:`sec_weight_decay`\n",
    "\n",
    "\n",
    "Now that we have characterized the problem of overfitting,\n",
    "we can introduce some standard techniques for regularizing models.\n",
    "Recall that we can always mitigate overfitting\n",
    "by going out and collecting more training data.\n",
    "That can be costly, time consuming,\n",
    "or entirely out of our control,\n",
    "making it impossible in the short run.\n",
    "For now, we can assume that we already have\n",
    "as much high-quality data as our resources permit\n",
    "and focus on regularization techniques.\n",
    "\n",
    "Recall that in our\n",
    "polynomial curve-fitting example\n",
    "(:numref:`sec_model_selection`)\n",
    "we could limit our model's capacity\n",
    "simply by tweaking the degree \n",
    "of the fitted polynomial.\n",
    "Indeed, limiting the number of features \n",
    "is a popular technique to avoid overfitting.\n",
    "However, simply tossing aside features\n",
    "can be too blunt an instrument for the job.\n",
    "Sticking with the polynomial curve-fitting\n",
    "example, consider what might happen\n",
    "with high-dimensional inputs.\n",
    "The natural extensions of polynomials\n",
    "to multivariate data are called *monomials*, \n",
    "which are simply products of powers of variables.\n",
    "The degree of a monomial is the sum of the powers.\n",
    "For example, $x_1^2 x_2$, and $x_3 x_5^2$ \n",
    "are both monomials of degree $3$.\n",
    "\n",
    "Note that the number of terms with degree $d$\n",
    "blows up rapidly as $d$ grows larger.\n",
    "Given $k$ variables, the number of monomials \n",
    "of degree $d$ is ${k - 1 + d} \\choose {k - 1}$.\n",
    "Even small changes in degree, say from $2$ to $3$,\n",
    "dramatically increase the complexity of our model.\n",
    "Thus we often need a more fine-grained tool\n",
    "for adjusting function complexity.\n",
    "\n",
    "## Squared Norm Regularization\n",
    "\n",
    "*Weight decay* (commonly called *L2* regularization),\n",
    "might be the most widely-used technique\n",
    "for regularizing parametric machine learning models.\n",
    "The technique is motivated by the basic intuition\n",
    "that among all functions $f$,\n",
    "the function $f = 0$ \n",
    "(assigning the value $0$ to all inputs) \n",
    "is in some sense the *simplest*,\n",
    "and that we can measure the complexity \n",
    "of a function by its distance from zero.\n",
    "But how precisely should we measure\n",
    "the distance between a function and zero?\n",
    "There is no single right answer.\n",
    "In fact, entire branches of mathematics,\n",
    "including parts of functional analysis \n",
    "and the theory of Banach spaces,\n",
    "are devoted to answering this issue.\n",
    "\n",
    "One simple interpretation might be \n",
    "to measure the complexity of a linear function\n",
    "$f(\\mathbf{x}) = \\mathbf{w}^\\top \\mathbf{x}$\n",
    "by some norm of its weight vector, e.g., $|| \\mathbf{w} ||^2$.\n",
    "The most common method for ensuring a small weight vector\n",
    "is to add its norm as a penalty term\n",
    "to the problem of minimizing the loss.\n",
    "Thus we replace our original objective,\n",
    "*minimize the prediction loss on the training labels*,\n",
    "with new objective,\n",
    "*minimize the sum of the prediction loss and the penalty term*.\n",
    "Now, if our weight vector grows too large,\n",
    "our learning algorithm might *focus* \n",
    "on minimizing the weight norm $|| \\mathbf{w} ||^2$\n",
    "versus minimizing the training error.\n",
    "That is exactly what we want.\n",
    "To illustrate things in code, \n",
    "let us revive our previous example\n",
    "from :numref:`sec_linear_regression` for linear regression.\n",
    "There, our loss was given by\n",
    "\n",
    "$$l(\\mathbf{w}, b) = \\frac{1}{n}\\sum_{i=1}^n \\frac{1}{2}\\left(\\mathbf{w}^\\top \\mathbf{x}^{(i)} + b - y^{(i)}\\right)^2.$$\n",
    "\n",
    "Recall that $\\mathbf{x}^{(i)}$ are the observations,\n",
    "$y^{(i)}$ are labels, and $(\\mathbf{w}, b)$\n",
    "are the weight and bias parameters respectively.\n",
    "To penalize the size of the weight vector,\n",
    "we must somehow add $|| \\mathbf{w} ||^2$ to the loss function,\n",
    "but how should the model trade off the \n",
    "standard loss for this new additive penalty?\n",
    "In practice, we characterize this tradeoff\n",
    "via the *regularization constant* $\\lambda > 0$, \n",
    "a non-negative hyperparameter \n",
    "that we fit using validation data:\n",
    "\n",
    "$$l(\\mathbf{w}, b) + \\frac{\\lambda}{2} \\|\\mathbf{w}\\|^2.$$\n",
    "\n",
    "For $\\lambda = 0$, we recover our original loss function.\n",
    "For $\\lambda > 0$, we restrict the size of $|| \\mathbf{w} ||$.\n",
    "The astute reader might wonder why we work with the squared\n",
    "norm and not the standard norm (i.e., the Euclidean distance).\n",
    "We do this for computational convenience.\n",
    "By squaring the L2 norm, we remove the square root, \n",
    "leaving the sum of squares of \n",
    "each component of the weight vector.\n",
    "This makes the derivative of the penalty easy to compute\n",
    "(the sum of derivatives equals the derivative of the sum).\n",
    "\n",
    "Moreover, you might ask why we work with the L2 norm \n",
    "in the first place and not, say, the L1 norm.\n",
    "\n",
    "In fact, other choices are valid and \n",
    "popular throughout statistics.\n",
    "While L2-regularized linear models constitute\n",
    "the classic *ridge regression* algorithm,\n",
    "L1-regularized linear regression\n",
    "is a similarly fundamental model in statistics\n",
    "(popularly known as *lasso regression*).\n",
    "\n",
    "More generally, the $\\ell_2$ is just one \n",
    "among an infinite class of norms call p-norms,\n",
    "many of which you might encounter in the future.\n",
    "In general, for some number $p$, \n",
    "the $\\ell_p$ norm is defined as\n",
    "\n",
    "$$\\|\\mathbf{w}\\|_p^p := \\sum_{i=1}^d |w_i|^p.$$\n",
    "\n",
    "\n",
    "One reason to work with the L2 norm\n",
    "is that it places and outsize penalty\n",
    "on large components of the weight vector.\n",
    "This biases our learning algorithm \n",
    "towards models that distribute weight evenly \n",
    "across a larger number of features.\n",
    "In practice, this might make them more robust\n",
    "to measurement error in a single variable.\n",
    "By contrast, L1 penalties lead to models\n",
    "that concentrate weight on a small set of features,\n",
    "which may be desirable for other reasons. \n",
    "\n",
    "The stochastic gradient descent updates \n",
    "for L2-regularized regression follow:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\mathbf{w} & \\leftarrow \\left(1- \\eta\\lambda \\right) \\mathbf{w} - \\frac{\\eta}{|\\mathcal{B}|} \\sum_{i \\in \\mathcal{B}} \\mathbf{x}^{(i)} \\left(\\mathbf{w}^\\top \\mathbf{x}^{(i)} + b - y^{(i)}\\right),\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "As before, we update $\\mathbf{w}$ based on the amount \n",
    "by which our estimate differs from the observation.\n",
    "However, we also shrink the size of $\\mathbf{w}$ towards $0$.\n",
    "That is why the method is sometimes called \"weight decay\":\n",
    "given the penalty term alone,\n",
    "our optimization algorithm *decays*\n",
    "the weight at each step of training.\n",
    "In contrast to feature selection,\n",
    "weight decay offers us a continuous mechanism\n",
    "for adjusting the complexity of $f$.\n",
    "Small values of $\\lambda$ correspond \n",
    "to unconstrained $\\mathbf{w}$,\n",
    "whereas large values of $\\lambda$ \n",
    "constrain $\\mathbf{w}$ considerably.\n",
    "Whether we include a corresponding bias penalty $b^2$ \n",
    "can vary across implementations, \n",
    "and may vary across layers of a neural network.\n",
    "Often, we do not regularize the bias term\n",
    "of a network's output layer.\n",
    " \n",
    "\n",
    "## High-Dimensional Linear Regression\n",
    "\n",
    "We can illustrate the benefits of \n",
    "weight decay over feature selection\n",
    "through a simple synthetic example.\n",
    "First, we generate some data as before\n",
    "\n",
    "$$y = 0.05 + \\sum_{i = 1}^d 0.01 x_i + \\epsilon \\text{ where }\n",
    "\\epsilon \\sim \\mathcal{N}(0, 0.01).$$\n",
    "\n",
    "choosing our label to be a linear function of our inputs,\n",
    "corrupted by Gaussian noise with zero mean and variance 0.01.\n",
    "To make the effects of overfitting pronounced,\n",
    "we can increase the dimensionality of our problem to $d = 200$\n",
    "and work with a small training set containing only 20 examples.\n",
    "\n",
    "We will now import the relevant libraries for showing weight decay concept in action."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "// %mavenRepo snapshots https://oss.sonatype.org/content/repositories/snapshots/\n",
    "\n",
    "%maven ai.djl:api:0.6.0\n",
    "%maven org.slf4j:slf4j-api:1.7.26\n",
    "%maven org.slf4j:slf4j-simple:1.7.26\n",
    "%maven net.java.dev.jna:jna:5.3.0\n",
    "%maven ai.djl.mxnet:mxnet-engine:0.6.0\n",
    "%maven ai.djl.mxnet:mxnet-native-auto:1.7.0-a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%loadFromPOM\n",
    "<dependency>\n",
    "    <groupId>tech.tablesaw</groupId>\n",
    "    <artifactId>tablesaw-jsplot</artifactId>\n",
    "    <version>0.30.4</version>\n",
    "</dependency>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load ../utils/plot-utils.ipynb\n",
    "%load ../utils/DataPoints.java\n",
    "%load ../utils/Training.java"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import ai.djl.*;\n",
    "import ai.djl.engine.*;\n",
    "import ai.djl.ndarray.*;\n",
    "import ai.djl.ndarray.types.Shape;\n",
    "import ai.djl.nn.*;\n",
    "import ai.djl.nn.core.Linear;\n",
    "import ai.djl.training.DefaultTrainingConfig;\n",
    "import ai.djl.training.GradientCollector;\n",
    "import ai.djl.training.Trainer;\n",
    "import ai.djl.training.dataset.ArrayDataset;\n",
    "import ai.djl.training.dataset.Batch;\n",
    "import ai.djl.training.evaluator.Accuracy;\n",
    "import ai.djl.training.listener.TrainingListener;\n",
    "import ai.djl.training.loss.L2Loss;\n",
    "import ai.djl.training.loss.Loss;\n",
    "import ai.djl.training.optimizer.Optimizer;\n",
    "import ai.djl.training.optimizer.learningrate.LearningRateTracker;\n",
    "import org.apache.commons.lang3.ArrayUtils;\n",
    "import tech.tablesaw.api.*;\n",
    "import tech.tablesaw.plotly.api.*;\n",
    "import tech.tablesaw.plotly.components.*;\n",
    "import tech.tablesaw.plotly.Plot;\n",
    "import tech.tablesaw.plotly.components.Figure;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "1"
    }
   },
   "outputs": [],
   "source": [
    "int nTrain = 20;\n",
    "int nTest = 100;\n",
    "int numInputs = 200;\n",
    "int batchSize = 5;\n",
    "\n",
    "float trueB = 0.05f;\n",
    "NDManager manager = NDManager.newBaseManager();\n",
    "NDArray trueW = manager.ones(new Shape(numInputs, 1));\n",
    "trueW = trueW.mul(0.01);\n",
    "\n",
    "public ArrayDataset loadArray(NDArray features, NDArray labels, int batchSize, boolean shuffle) {\n",
    "    return new ArrayDataset.Builder()\n",
    "                  .setData(features) // set the features\n",
    "                  .optLabels(labels) // set the labels\n",
    "                  .setSampling(batchSize, shuffle) // set the batch size and random sampling\n",
    "                  .build();\n",
    "}\n",
    "\n",
    "DataPoints trainData = DataPoints.syntheticData(manager, trueW, trueB, nTrain);\n",
    "\n",
    "ArrayDataset trainIter = loadArray(trainData.getX(), trainData.getY(), batchSize, true);\n",
    "\n",
    "DataPoints testData = DataPoints.syntheticData(manager, trueW, trueB, nTest);\n",
    "\n",
    "ArrayDataset testIter = loadArray(testData.getX(), testData.getY(), batchSize, false);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementation from Scratch\n",
    "\n",
    "Next, we will implement weight decay from scratch,\n",
    "simply by adding the squared $\\ell_2$ penalty\n",
    "to the original target function.\n",
    "\n",
    "### Initializing Model Parameters\n",
    "\n",
    "First, we will define a function \n",
    "to randomly initialize our model parameters \n",
    "and run `attachGradient()` on each to allocate \n",
    "memory for the gradients we will calculate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "2"
    }
   },
   "outputs": [],
   "source": [
    "public class InitParams{\n",
    "\n",
    "    private NDArray w;\n",
    "    private NDArray b;\n",
    "    private NDList l;\n",
    "    \n",
    "    public NDArray getW(){\n",
    "        return this.w;\n",
    "    }\n",
    "    \n",
    "    public NDArray getB(){\n",
    "        return this.b;\n",
    "    }\n",
    "    \n",
    "    public InitParams(){\n",
    "        NDManager manager = NDManager.newBaseManager();\n",
    "        w = manager.randomNormal(0, 1.0f, new Shape(numInputs, 1), DataType.FLOAT32, Device.defaultDevice());\n",
    "        b = manager.zeros(new Shape(1));\n",
    "        w.attachGradient();\n",
    "        b.attachGradient();\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Defining $\\ell_2$ Norm Penalty\n",
    "\n",
    "Perhaps the most convenient way to implement this penalty\n",
    "is to square all terms in place and sum them up.\n",
    "We divide by $2$ by convention\n",
    "(when we take the derivative of a quadratic function,\n",
    "the $2$ and $1/2$ cancel out, ensuring that the expression\n",
    "for the update looks nice and simple)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "3"
    }
   },
   "outputs": [],
   "source": [
    "public NDArray l2Penalty(NDArray w){\n",
    "    return ((w.pow(2)).sum()).div(2);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "Loss l2loss = Loss.l2Loss();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Defining the Train and Test Functions\n",
    "\n",
    "The following code fits a model on the training set\n",
    "and evaluates it on the test set.\n",
    "The linear network and the squared loss\n",
    "have not changed since the previous chapter,\n",
    "so we will just import them via `Training.linreg()` and `Training.squaredLoss()`.\n",
    "The only change here is that our loss now includes the penalty term."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "4"
    }
   },
   "outputs": [],
   "source": [
    "float[] trainLoss;\n",
    "float[] testLoss;\n",
    "float[] epochCount;\n",
    "\n",
    "public void train(float lambd){\n",
    "    \n",
    "    InitParams initParams = new InitParams();\n",
    "    \n",
    "    NDList params = new NDList(initParams.getW(), initParams.getB());\n",
    "    \n",
    "    int numEpochs = 100;\n",
    "    float lr = 0.003f;\n",
    "    \n",
    "    trainLoss = new float[(numEpochs/5)];\n",
    "    testLoss = new float[(numEpochs/5)];\n",
    "    epochCount = new float[(numEpochs/5)];\n",
    "\n",
    "    for(int epoch = 1; epoch <= numEpochs; epoch++){\n",
    "        \n",
    "        for(Batch batch : trainIter.getData(manager)){\n",
    "            \n",
    "            NDArray X = batch.getData().head();\n",
    "            NDArray y = batch.getLabels().head();\n",
    "            \n",
    "            // Attach Gradients\n",
    "            for(NDArray param : params) {\n",
    "                param.attachGradient();\n",
    "            }\n",
    "            \n",
    "             NDArray w = params.get(0);\n",
    "             NDArray b = params.get(1);\n",
    "            \n",
    "            try (GradientCollector gc = Engine.getInstance().newGradientCollector()) {\n",
    "                // Minibatch loss in X and y\n",
    "                NDArray l = Training.squaredLoss(Training.linreg(X, w, b), y).add(l2Penalty(w).mul(lambd));\n",
    "                gc.backward(l);  // Compute gradient on l with respect to w and b\n",
    "                Training.sgd(params, lr, batchSize);  // Update parameters using their gradient\n",
    "            }\n",
    "            \n",
    "            batch.close();\n",
    "        }\n",
    "        \n",
    "        if(epoch % 5 == 0){\n",
    "            NDArray testL = Training.squaredLoss(Training.linreg(testData.getX(), params.get(0), params.get(1)), testData.getY());\n",
    "            NDArray trainL = Training.squaredLoss(Training.linreg(trainData.getX(), params.get(0), params.get(1)), trainData.getY());\n",
    "            \n",
    "            epochCount[epoch/5 - 1] = epoch;  \n",
    "            trainLoss[epoch/5 -1] = trainL.mean().getFloat();\n",
    "            testLoss[epoch/5 -1] = testL.mean().getFloat();\n",
    "        }\n",
    "        \n",
    "    }\n",
    "    \n",
    "    System.out.println(\"l1 norm of w: \" + params.get(0).abs().sum());\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training without Regularization\n",
    "\n",
    "We now run this code with `lambd = 0`, \n",
    "disabling weight decay.\n",
    "Note that we overfit badly, \n",
    "decreasing the training error but not the \n",
    "test error---a textook case of overfitting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "5"
    }
   },
   "outputs": [],
   "source": [
    "train(0f);\n",
    "\n",
    "String[] lossLabel = new String[trainLoss.length + testLoss.length];\n",
    "\n",
    "Arrays.fill(lossLabel, 0, testLoss.length, \"test\");\n",
    "Arrays.fill(lossLabel, testLoss.length, trainLoss.length + testLoss.length, \"train\");\n",
    "\n",
    "Table data = Table.create(\"Data\").addColumns(\n",
    "      FloatColumn.create(\"epochCount\", ArrayUtils.addAll(epochCount, epochCount)), \n",
    "      FloatColumn.create(\"loss\", ArrayUtils.addAll(testLoss, trainLoss)),\n",
    "      StringColumn.create(\"lossLabel\", lossLabel)\n",
    ");\n",
    "\n",
    "render(LinePlot.create(\"\", data, \"epochCount\", \"loss\", \"lossLabel\"),\"text/html\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Using Weight Decay\n",
    "\n",
    "Below, we run with substantial weight decay.\n",
    "Note that the training error increases\n",
    "but the test error decreases.\n",
    "This is precisely the effect \n",
    "we expect from regularization.\n",
    "As an exercise, you might want to check\n",
    "that the $\\ell_2$ norm of the weights $\\mathbf{w}$\n",
    "has actually decreased."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "6"
    }
   },
   "outputs": [],
   "source": [
    "// calling training with weight decay lambda = 3.0\n",
    "train(3f);\n",
    "\n",
    "String[] lossLabel = new String[trainLoss.length + testLoss.length];\n",
    "\n",
    "Arrays.fill(lossLabel, 0, testLoss.length, \"test\");\n",
    "Arrays.fill(lossLabel, testLoss.length, trainLoss.length + testLoss.length, \"train\");\n",
    "\n",
    "Table data = Table.create(\"Data\").addColumns(\n",
    "      FloatColumn.create(\"epochCount\", ArrayUtils.addAll(epochCount, epochCount)), \n",
    "      FloatColumn.create(\"loss\", ArrayUtils.addAll(testLoss, trainLoss)),\n",
    "      StringColumn.create(\"lossLabel\", lossLabel)\n",
    ");\n",
    "\n",
    "render(LinePlot.create(\"\", data, \"epochCount\", \"loss\", \"lossLabel\"),\"text/html\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Concise Implementation\n",
    "\n",
    "Because weight decay is ubiquitous \n",
    "in neural network optimization,\n",
    "DJL makes it especially convenient,\n",
    "integrating weight decay into the optimization algorithm itself\n",
    "for easy use in combination with any loss function.\n",
    "Moreover, this integration serves a computational benefit,\n",
    "allowing implementation tricks to add weight decay to the algorithm,\n",
    "without any additional computational overhead.\n",
    "Since the weight decay portion of the update\n",
    "depends only on the current value of each parameter,\n",
    "and the optimizer must touch each parameter once anyway.\n",
    "\n",
    "In the following code, we specify\n",
    "the weight decay hyperparameter directly\n",
    "through `wd` when instantiating our `Trainer`.\n",
    "By default, DJL decays both \n",
    "weights and biases simultaneously."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "7"
    }
   },
   "outputs": [],
   "source": [
    "public void train_djl(float wd){\n",
    "    \n",
    "    InitParams initParams = new InitParams();\n",
    "    \n",
    "    NDList params = new NDList(initParams.getW(), initParams.getB());\n",
    "    \n",
    "    int numEpochs = 100;\n",
    "    float lr = 0.003f;\n",
    "    \n",
    "    trainLoss = new float[(numEpochs/5)];\n",
    "    testLoss = new float[(numEpochs/5)];\n",
    "    epochCount = new float[(numEpochs/5)];\n",
    "    \n",
    "    LearningRateTracker lrt = LearningRateTracker.fixedLearningRate(lr);\n",
    "    Optimizer sgd = Optimizer.sgd().setLearningRateTracker(lrt).build();\n",
    "    \n",
    "    DefaultTrainingConfig config = new DefaultTrainingConfig(l2loss)\n",
    "     .optOptimizer(sgd) // Optimizer (loss function)\n",
    "     .addEvaluator(new Accuracy()) // Model Accuracy\n",
    "     .addEvaluator(l2loss)\n",
    "     .addTrainingListeners(TrainingListener.Defaults.logging()); // Logging\n",
    "    \n",
    "    Model model = Model.newInstance(\"mlp\");\n",
    "\n",
    "    SequentialBlock net = new SequentialBlock();\n",
    "    Linear linearBlock = Linear.builder().optBias(true).setOutChannels(1).build();\n",
    "    net.add(linearBlock);\n",
    "\n",
    "    model.setBlock(net);\n",
    "    Trainer trainer = model.newTrainer(config);\n",
    "        \n",
    "    trainer.initialize(new Shape(batchSize, 2));\n",
    "    for(int epoch = 1; epoch <= numEpochs; epoch++){\n",
    "        \n",
    "        for(Batch batch : trainer.iterateDataset(trainIter)){\n",
    "            \n",
    "            NDArray X = batch.getData().head();\n",
    "            NDArray y = batch.getLabels().head();\n",
    "            \n",
    "            // Attach Gradients\n",
    "             for (NDArray param : params) {\n",
    "                 param.attachGradient();\n",
    "             }\n",
    "             NDArray w = params.get(0);\n",
    "             NDArray b = params.get(1);\n",
    "            \n",
    "            try (GradientCollector gc = Engine.getInstance().newGradientCollector()) {\n",
    "                // Minibatch loss in X and y\n",
    "                NDArray l = Training.squaredLoss(Training.linreg(X, w, b), y).add(l2Penalty(w).mul(wd));\n",
    "                gc.backward(l);  // Compute gradient on l with respect to w and b\n",
    "                Training.sgd(params, lr, batchSize);  // Update parameters using their gradient\n",
    "            }\n",
    "            batch.close();\n",
    "        }\n",
    "        \n",
    "        if(epoch % 5 == 0){\n",
    "            NDArray testL = Training.squaredLoss(Training.linreg(testData.getX(), params.get(0), params.get(1)), testData.getY());\n",
    "            NDArray trainL = Training.squaredLoss(Training.linreg(trainData.getX(), params.get(0), params.get(1)), trainData.getY());\n",
    "            \n",
    "            epochCount[epoch/5 - 1] = epoch;  \n",
    "            trainLoss[epoch/5 -1] = trainL.mean().getFloat();\n",
    "            testLoss[epoch/5 -1] = testL.mean().getFloat();\n",
    "        }\n",
    "        \n",
    "    }\n",
    "    System.out.println(\"l1 norm of w: \" + params.get(0).abs().sum());\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The plots look identical to those when \n",
    "we implemented weight decay from scratch.\n",
    "However, they run appreciably faster \n",
    "and are easier to implement,\n",
    "a benefit that will become more\n",
    "pronounced for large problems."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "8"
    }
   },
   "outputs": [],
   "source": [
    "train_djl(0);\n",
    "\n",
    "String[] lossLabel = new String[trainLoss.length + testLoss.length];\n",
    "\n",
    "Arrays.fill(lossLabel, 0, testLoss.length, \"test\");\n",
    "Arrays.fill(lossLabel, testLoss.length, trainLoss.length + testLoss.length, \"train\");\n",
    "\n",
    "Table data = Table.create(\"Data\").addColumns(\n",
    "      FloatColumn.create(\"epochCount\", ArrayUtils.addAll(epochCount, epochCount)), \n",
    "      FloatColumn.create(\"loss\", ArrayUtils.addAll(testLoss, trainLoss)),\n",
    "      StringColumn.create(\"lossLabel\", lossLabel)\n",
    ");\n",
    "\n",
    "render(LinePlot.create(\"\", data, \"epochCount\", \"loss\", \"lossLabel\"),\"text/html\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far, we only touched upon one notion of\n",
    "what constitutes a simple *linear* function.\n",
    "Moreover, what constitutes a simple *nonlinear* function\n",
    "can be an even more complex question.\n",
    "For instance, [Reproducing Kernel Hilbert Spaces (RKHS)](https://en.wikipedia.org/wiki/Reproducing_kernel_Hilbert_space)\n",
    "allows one to apply tools introduced \n",
    "for linear functions in a nonlinear context.\n",
    "Unfortunately, RKHS-based algorithms\n",
    "tend to scale purely to large, high-dimensional data.\n",
    "In this book we will default to the simple heuristic\n",
    "of applying weight decay on all layers of a deep network.\n",
    "\n",
    "## Summary\n",
    "\n",
    "* Regularization is a common method for dealing with overfitting. It adds a penalty term to the loss function on the training set to reduce the complexity of the learned model.\n",
    "* One particular choice for keeping the model simple is weight decay using an $\\ell_2$ penalty. This leads to weight decay in the update steps of the learning algorithm.\n",
    "* DJL provides automatic weight decay functionality in the optimizer by setting the hyperparameter `wd`.\n",
    "* You can have different optimizers within the same training loop, e.g., for different sets of parameters.\n",
    "\n",
    "\n",
    "## Exercises\n",
    "\n",
    "1. Experiment with the value of $\\lambda$ in the estimation problem in this page. Plot training and test accuracy as a function of $\\lambda$. What do you observe?\n",
    "1. Use a validation set to find the optimal value of $\\lambda$. Is it really the optimal value? Does this matter?\n",
    "1. What would the update equations look like if instead of $\\|\\mathbf{w}\\|^2$ we used $\\sum_i |w_i|$ as our penalty of choice (this is called $\\ell_1$ regularization).\n",
    "1. We know that $\\|\\mathbf{w}\\|^2 = \\mathbf{w}^\\top \\mathbf{w}$. Can you find a similar equation for matrices (mathematicians call this the [Frobenius norm](https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm))?\n",
    "1. Review the relationship between training error and generalization error. In addition to weight decay, increased training, and the use of a model of suitable complexity, what other ways can you think of to deal with overfitting?\n",
    "1. In Bayesian statistics we use the product of prior and likelihood to arrive at a posterior via $P(w \\mid x) \\propto P(x \\mid w) P(w)$. How can you identify $P(w)$ with regularization?\n",
    "\n",
    "## [Discussions](https://discuss.mxnet.io/t/2342)\n",
    "\n",
    "![](../img/qr_weight-decay.svg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java",
   "language": "java",
   "name": "java"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "Java",
   "pygments_lexer": "java",
   "version": "11.0.5+10-LTS"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
